【超详细!】一文看懂从逻辑回归(Logistic)到神经网络(NN)
引言
之前的文章,我们详细介绍了线性回归,逻辑回归,通过线性回归可以做数值的预测,通过逻辑回归,可以解决二分类(线性可分、线性不可分)问题,那么对于多分类问题应该怎么解决呢?除此之外,作为深度学习入门必然要接触到的知识点,它们和神经网络又有啥关系呢?多分类的问题,明明可以通过单层网络解决为什么要用到多层呢?
带着上面这些问题,结合吴恩达和李宏毅的讲义以及自己的理解整理了这篇文章,希望能给大家一些启发。另外文章里面的代码可能不方便复制粘贴,这篇文章同步更新到了知乎,知乎截图如下。最后觉得不错的话,再给我点个赞、在看吧~
从二分类到多分类
首先针对二元分类即0-1分类,主要利用sigmod函数(也可以是其它激活函数)将特征变量映射到[0,1]内,并以此来决定输出结果是0和1的可能性。比如:根据成绩判断该学生是否可以被录取,当输出的结果为0.7时,那么该学生将有70%的概率被录取和30%不被录取的概率;根据神奇宝贝特征判断是否属于火属性,当输出的结果为0.8时,那么该神奇宝贝将有80%的概率属于火属性和20%非火属性。
然后对于多元分类,其实多元分类可转化为多次二元分类来解决问题,拿动物分类来说吧,比如有:猫、狗、猪等三类需要区分,这三类可分别用y=0、y=1、y=2来代表,分别对应下图种的黄色图标、绿色图标、蓝色图标。
如上图,
1、要区分黄色图标,将黄色图标认为是1类,将蓝色图标和绿色图标认为是其它类,此时逻辑回归模型记为:,当输出越大图标为黄色概率也就越大;
2、要区分绿色图标,将黄色图标认为是2类,将黄色图标和蓝色图标认为是其它类,此时逻辑回归模型记为:,当输出越大图标为绿色概率也就越大;
3、要区分蓝色图标,将蓝色图标认为是3类,将黄色图标和绿色图标认为是其它类,此时逻辑回归模型记为:,当输出越大图标为蓝色概率也就越大;
4、按照以上规则,当有特征x输入的时候,可以分别求取,然后取最大值:
5、从三类延伸至3类以上,其实也是一样的道理,对于特征X输入,求取:
公式逻辑推导
逻辑回归的预测函数为:
其中
表达式写成以下形式:
我们可以从三分类推广到更多的分类,公式如下所示:
将上式写成矩阵相乘的方式:
其中:表示输入特征的个数,表示需要区分的种类,表示权重参数,的下表第一个数字表示该层神经单元的编号,第二数字表示输入特征编号,我们可以得出
找出最大值对应的标记分类,就可以实现多分类。其实这里的m和n是相等的,但是为什么区分开呢?主要是因为当涉及多层网络叠加的时候就会出现不等的情况(例如:输入样本的特征值有100个,共计分类个数为10个)。
将上面公式对应图形中如下图所示,这里你可能会想偏置项为什么不一样呢,其实对于偏置项b,在实际应用中对应的是和权重参数,就是上面权重参数矩阵的第一列。(请忽略我拙劣的画图技巧~
逻辑回归多分类代码
这里引用吴恩达老师的课后练习,即通过多分类逻辑回归实现手写数字分类。其中训练数据集(ex3data1.mat)为:5000张数字像素图片,每个像素图片的像素大小为:20 X 20,标注分别对应图片中的数字(「注意:这里手写体图片0对应的标注数据为1」)。
# 逻辑回归多分类代码--手写字体识别
# 读取数据集,打印数据集大小
import numpy as np
import matplotlib.pyplot as plt
import scipy.io as sio
data = sio.loadmat('../input/wu-minist/ex3data1.mat')
raw_X = data['X'] # 5000张图片
raw_y = data['y'] # 对应的标注
print("The raw_X shape is: ",raw_X.shape)
print("The raw_y shape is: ",raw_y.shape)
#随机获取一张图片打印
pick_one = np.random.randint(raw_X.shape[0])
image = raw_X[pick_one,:]
fig,ax = plt.subplots(figsize=(1,1))
ax.imshow(image.reshape(20,20).T,cmap = 'gray_r')
# 去除图片横纵坐标
plt.xticks([])
plt.yticks([])
在数据可见的情况下,「能观察数据尽量观察一下数据」,这样有助于后面的分析,所以这里随机选择100个数字进行观察。
def plot_100_image(X):
# 0-5000的索引随机选取100个数字,用作图片索引
sample_index = np.random.choice(len(X),36)
images = X[sample_index,:]
print(images.shape)
fig,ax = plt.subplots(ncols=6,nrows=6,figsize=(8,8),sharex=True,sharey=True)
for r in range(6):
for c in range(6):
ax[r,c].imshow(images[6 * r + c].reshape(20,20).T,cmap='gray_r')
plt.xticks([])
plt.yticks([])
plt.show
plot_100_image(raw_X)
以下是多分类非线性回归训练用到的损失函数、训练函数、梯度计算函数、预测函数等。
# 需要用到的函数sigmod函数、
def sigmod(z):
return 1 / (1 + np.exp(-z))
# 损失函数计算韩式
def no_line_lossfunc(x,y,theta,lamda):
tmp_a=1/len(x)*y*np.log(sigmod(x@theta))+(1-y)*np.log(1-sigmod(x@theta))
tmp_b=lamda/(2*len(x))*np.sum(theta)
return -(tmp_a+tmp_b)
# 非线性回归梯度计算+正则化项
def no_line_gradent(x,y,theta,lamda):
tmp=1/len(x)*x.T.dot(sigmod(x@theta)-y)
# 正则化项,为什么要把theta0排查,因为它是一个偏置项,并不是权重参数。
# reg=lamda/len(x)*theta
reg=lamda/len(x)*theta[1:]
reg=np.insert(reg,0,0,0)
return tmp+reg
# 非线性回归训练函数
def no_line_train(X,Y,lr,epoches,batch_size,lamda):
loss_cost=[]
m=X.shape[0]
n=X.shape[1]
theta=np.zeros((401,10)) # 图片分为10类,每张图片像素20*20=400,并考虑偏置项b 以此初始化theta
for epoch in range(epoches):
shuffle_index=np.random.permutation(m)
x_shuffle=X[shuffle_index]
y_shuffle=Y[shuffle_index]
for i in range(0,m,batch_size):
x_train=x_shuffle[i:i+batch_size]
y_train=y_shuffle[i:i+batch_size]
gradent=no_line_gradent(x_train,y_train,theta,lamda)
theta=theta-lr*gradent
if epoch % 1000 == 0:
loss_cost.append(no_line_lossfunc(x_shuffle,y_shuffle,theta,lamda))
return theta,loss_cost
# 预测函数
def predict(X,theta_final):
h = sigmod(X@theta_final) #(5000,401) (0,401) =>(5000,10)
h_argmax = np.argmax(h,axis=1) # 按照行找出最大值的索引值号
return h_argmax + 1
开始多分类逻辑回归的训练。
# 将数据集放入模型进行训练
epoches = 20
batch_size=2000
alpha=0.1
lamda=1
x=np.insert(raw_X,0,values=1,axis=1)
print("x.shape is :",x.shape)
y=raw_y.flatten()
y_tem=np.unique(y)
print(y_tem)
m=len(y)
y_mat=np.zeros((10,m))
for i in range(m):
if y[i]==10:
y_mat[0][i]=1
else:
y_mat[y[i]][i]=1
print(y_mat.shape)
final_theta,loss_cost=no_line_train(x,y_mat.T,alpha,epoches,batch_size,lamda)
print("The theta shape is {},and the theta is: {}".format(final_theta.shape,final_theta))
print(x.shape,final_theta.shape)
y_pred = predict(x,final_theta)
acc = np.mean(y_pred == y)
print("The acc is {}".format(acc))
从单层到多层
为什么说深度学习比传统机器学习好?
还记得前两节中:线性回归中的多元线性回归和逻辑回归中的线性不可分吗?当时在学习的时候是否有想过这两个问题,「多元线性回归中的demo为什要把输入特征x做平方之后才能得到好的结果?逻辑回归线性不可分的demo中,为什么要把输入特征做拓展才能得到比较好的结果呢?」 这主要是因为,在多元线性回归demo中,看到输入特征画出来的分布比较符合二次元函数,所以将输入特征做了平方;在逻辑回归不可分demo中,输入特征并不是线性的,不是我们熟悉的一般函数,所以对特征进行了拓展。这两个只是一般简单的例子,当应用到实际环境中,特征有十几个甚至上百个,如何才能找到构造比较特征呢?这就是传统机器学习的弊端。而神经网络就不存在这个问题,在深度神经网络中通过复杂的网络计算可以从很多原始特征中自己学出高级特征,从而达到更好的拟合效果。这些高级特征很多都是之前人工无法构造出来的,所以在特征越原始越多的领域深度学习相比传统机器学习的优势越大。
多层神经网络
按照上面的单层网络,我们推广到多层,即:将各个单层的网络串联到一块,当前层的输出就是下一层的输入,这就形成了深度学习网络(全连接),如下图所示。(请忽略我拙劣的画图技巧~
上图公式中:共计涉及三个权重参数矩阵,第一个矩阵表示最后输出层的权重参数,第二个表示模型中间层的某个权重can'h模型共计层,每个关于的矩阵都表示模型中的其中一层,表示模型的第j层网络,表示要分类的个数,n表示输入特征的个数,对于初学者可能对于有一些困惑,它不是特征值而是一个常数,主要是用来拟合偏置常数b。
代码实现
前面线性回归、逻辑回归都是手撕代码,每个函数都要自己实现。进入神经网络,我们就要考虑使用框架了。目前主流的框架有:tensflow、pytorch、paddlepaddle等,每种框架都有自己的优缺点。这里我们采用pytorch框架,因为它的计算训练比较灵活,和我们经常用的numpy也能更好的契合。
pytorch数据集制作。转换成Dataset类,方面后续转入Dataloader类
from torch.utils.data import Dataset
class CustomImageDataset(Dataset):
def __init__(self, date_feature,data_target ):
self.date_feature = date_feature
self.data_target = data_target
def __len__(self):
return len(self.date_feature)
def __getitem__(self, idx):
return self.date_feature[idx], self.data_target[idx]
pytorch 实现多分类逻辑回归
import torch
import torch.nn as nn
import torch.utils.data as Data
import numpy as np
import matplotlib.pyplot as plt
from torch.autograd import Variable
from torch.utils.data import DataLoader
device = "cuda" if torch.cuda.is_available() else "cpu"
# 读入数据,数据转换成torch格式
import scipy.io as sio
data = sio.loadmat('../input/wu-minist/ex3data1.mat')
x_data=torch.Tensor(data['X'])
y_data=torch.from_numpy(data['y'])
# y_data=torch.Tensor(data['y'],dtype=torch.int)
y_data=torch.squeeze(y_data)
for i in range(len(y_data)):
if y_data[i]==10:
y_data[i]=0
# tmp=torch.unique(y_data)
# print(tmp)
print("y_data data type is :",y_data.shape)
train_dateset=CustomImageDataset(x_data,y_data)
train_loader = DataLoader(dataset=train_dateset, batch_size=64, shuffle=True)
train_features, train_labels = next(iter(train_loader))
## 模型输入特征个数、输出种类
input_size=x_data.shape[1]
print("input_size:",input_size)
num_classes=10
## 模型超参数
learning_rate=1.0
epochs=5
# 创建逻辑回归类模型 (sigmoid(wx+b))
class LogisticRegression(nn.Module):
def __init__(self,input_size,num_classes):
super(LogisticRegression,self).__init__()
self.linear = nn.Linear(input_size,num_classes)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
out1 = self.linear(x)
out2 = self.sigmoid(out1)
return out2
# 设定模型参数
model = LogisticRegression(input_size, num_classes).to(device)
print('The model is :',model)
# 定义损失函数,分类任务,使用交叉熵
criterion = nn.CrossEntropyLoss()
# 优化算法,随机梯度下降,lr为学习率,获得模型需要更新的参数值
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
# 使用训练数据训练模型
for epoch in range(epochs):
# 批量数据进行模型训练
for i, (x_in, labels) in enumerate(train_loader):
# 需要将数据转换为张量Variable
x_in, labels = x_in.to(device), labels.to(device)
# 梯度更新前需要进行梯度清零
optimizer.zero_grad()
# 获得模型的训练数据结果
outputs = model(x_in)
# 计算损失函数用于计算梯度
loss = criterion(outputs, labels)
# 计算梯度
loss.backward()
# 进行梯度更新
optimizer.step()
# 每隔一段时间输出一个训练结果
if (i+1) % 10 == 0:
print('Epoch:[%d %d], Step:[%d/%d], Loss: %.4f' % (epoch+1,epochs,i+1,len(train_dateset)//batch_size,loss.item()))
推荐阅读
[1] 「自然语言处理(NLP)」 你必须要知道的 “ 十二个国际顶级会议 ” !
[2] 2023年!自然语言处理(NLP)10 大预训练模型
[4] ChatGPT要怎么微调?MIT韩松团队新作告诉你!